本文最后更新于:2024年4月25日 上午
                  
                
              
            
            
              
                
                设计思路

新建OAuth Apps
打开GitHub->setting->Developer settings->OAuth Apps
点New OAuth App新建一个应用

博客后台
路由表
router目录下的modules新建login.ts
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 
 | export const Login = [{
 path: '/login',
 name: 'login',
 component: () => import('@/views/login/index.vue'),
 }, {
 
 path: '/login/code',
 component: () => import('@/views/login/callback/index.vue')
 }]
 
 | 
router/index.ts导入login.ts
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 
 | import { createRouter, createWebHistory } from 'vue-router'import { Login } from './modules/login'
 
 const routes = [
 {
 path: '/',
 redirect: '/login'
 },
 {
 path: '/home',
 
 },
 ...Login
 ]
 
 export const router = createRouter({
 history: createWebHistory(),
 routes
 })
 
 | 
登录页
iconfont图标库
注册iconfont
选择图标添加到项目,打开我的项目,选择Symbol

打开链接,复制Javascript代码
assets新建fonts目录,新建fonts/index.js
把上一步链接代码复制到index.js
main.ts导入图标js
app.vue设置全局class="icon"css
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 
 | <template><router-view></router-view>
 </template>
 
 <style lang="scss">
 body {
 margin: 0;
 }
 
 .icon {
 vertical-align: -0.15em;
 fill: currentColor;
 overflow: hidden;
 }
 </style>
 
 | 
使用
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 
 | <template><svg class="icon" aria-hidden="true">
 
 <use xlink:href="#icon-GitHub"></use>
 </svg>
 </template>
 
 <style lang="scss" scoped>
 
 .icon {
 width: 2rem;
 height: 2rem;
 }
 </style>
 
 | 
登录页面
views目录下新建login目录,新建index.vue作为登录页
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 
 | <template><div class="container">
 <el-card class="box-card">
 <template #header>
 <div class="card-header">
 <span>管理员认证</span>
 <el-button class="button" type="text">使用说明</el-button>
 </div>
 </template>
 <div class="authorization">
 
 <a
 href="https://github.com/login/oauth/authorize?client_id=db7547efa91798d7e956"
 title="GitHub授权认证">
 <i class="iconfont icon-GitHub"></i>
 </a>
 </div>
 <el-divider>第三方授权认证</el-divider>
 </el-card>
 </div>
 </template>
 
 <style lang="scss" scoped>
 .container {
 width: 100%;
 position: fixed;
 top: 0;
 bottom: 0;
 display: flex;
 justify-content: center;
 align-items: center;
 }
 
 .authorization {
 display: flex;
 justify-content: center;
 align-items: center;
 width: 100%;
 height: 100%;
 
 a { color: #000; text-decoration: none; }
 
 a > i { font-size: 40px; }
 }
 
 .card-header { display: flex; justify-content: space-between; align-items: center; }
 
 .text { font-size: 14px; }
 
 .item { margin-bottom: 18px; }
 
 .box-card { width: 480px; }
 </style>
 
 | 
回调中转页
login文件夹下新建callback文件夹,新建index.vue作为回调中转页面
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 
 | <script lang="ts" setup>import { router } from '@/router'
 import { ApiGet } from '@/utils/request'
 import { ElMessage, ElLoading } from 'element-plus'
 import { onMounted } from 'vue';
 
 onMounted(async () => {
 
 const loading = ElLoading.service({
 lock: true,
 text: '正在人海中搜寻...',
 background: '#fff',
 })
 
 
 
 const params = new URLSearchParams(location.search)
 const code = params.get('code')
 
 const res = (await ApiGet('/login/token', { code })).data
 
 
 if (res.statusCode === 2000) {
 
 ElMessage.success('欢迎回家φ(゜▽゜*)♪')
 loading.close()
 router.push({ path: '/home' })
 } else {
 
 setTimeout(() => {
 ElMessage.error('授权失败ψ(`∇´)ψ')
 loading.close()
 router.push({ path: '/login' })
 }, 2000)
 }
 })
 </script>
 
 | 
后端
配置环境变量
用.env文件存储client_id与client_secret
根目录新建.env文件

| 12
 3
 
 | CLIENT_ID: "上一步的client_id"CLIENT_SECRET: "上一步的client_secret"
 USER_ID: 666666<管理员的GitHubID>
 
 | 
.gitignore添加.env,这样上传到仓库就看不到密匙了
使用环境变量
| 12
 3
 
 | import dotenv from 'dotenv'
 const config = dotenv.config().parsed
 
 | 
安装axios
第三方授权需要请求信息,所以使用axios请求
src目录下新建utils文件夹,新建request.ts
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 
 | import axios from 'axios'
 const ApiGet = (url: string, params: any, config?: any) => {
 return axios.get(url, {
 ...params,
 ...config
 })
 }
 
 const ApiPost = (url: string, data: any, config?: any) => {
 return axios.post(url, data, config)
 }
 
 export { ApiPost, ApiGet }
 
 | 
提供授权
获取token
api文件夹下新建types文件夹,新建index.ts
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 
 | interface Params {
 client_id: string,
 client_secret: string,
 code: string
 }
 
 
 interface Message {
 login: string,
 id: number,
 token: string,
 avatar_url: string
 }
 
 
 interface ResponseData {
 statusCode: number,
 message: string,
 data: Message | null
 }
 
 export { Params, ResponseData }
 
 | 
login文件夹下新建getGithubToken.ts
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 
 | import { Request, Response } from 'express'import { ApiPost, ApiGet } from '../../utils/request'
 import { Params, ResponseData } from './types'
 import dotenv from 'dotenv'
 
 const config = dotenv.config().parsed
 
 const params: Params = {
 client_id: config!.CLIENT_ID,
 client_secret: config!.CLIENT_SECRET,
 code: '',
 }
 
 const responseData: ResponseData = {
 statusCode: 2000,
 message: 'success',
 data: null,
 }
 
 const setResponseData = (newData: ResponseData) => {
 responseData.statusCode = newData.statusCode
 responseData.message = newData.message
 responseData.data = newData.data
 }
 
 export const getGithubToken = {
 GitToken: async (req: Request, res: Response) => {
 
 params.code = String(req.query.code)
 
 
 let token = ''
 try {
 const data = await ApiPost('https://github.com/login/oauth/access_token', params)
 token = data.data.split('&')[0].split('=')[1]
 } catch (e: any) {
 setResponseData({ statusCode: 5001, message: '获取GitHub token失败', data: null })
 }
 
 
 try {
 
 const userData = await ApiGet('https://api.github.com/user', {
 headers: {
 'Authorization': 'token ' + token
 }
 })
 
 
 if (userData.status === 200 && userData.data.login) {
 
 if (userData.data.id != config!.USER_ID)
 setResponseData({ statusCode: 4003, message: '用户信息不匹配', data: null })
 else
 setResponseData({
 statusCode: 2000,
 message: 'success',
 data: {
 login: userData.data.login,
 id: userData.data.id,
 token: token,
 avatar_url: userData.data.avatar_url,
 }
 })
 } else
 setResponseData({ statusCode: 4001, message: '获取用户信息失败', data: null })
 } catch (e: any) {
 setResponseData({ statusCode: 5002, message: '无效token', data: null })
 }
 
 res.send(responseData)
 }
 }
 
 | 
router目录下index.ts注册/login/token路径
| 12
 3
 4
 5
 6
 7
 8
 9
 
 | import express, { Router } from 'express'import { getGithubToken } from '../api/login/getGithubToken'
 
 const router: Router = express.Router()
 
 
 router.get('/login/token', getGithubToken.GitToken)
 
 export default router
 
 | 
最终效果
登录页

授权页

中转页

跳回app首页
